package org.zoomdev.zoom;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.zoomdev.zoom.aop.Constructor;
import org.zoomdev.zoom.aop.Setter;
import org.zoomdev.zoom.aop.factory.ConstructorFactory;
import org.zoomdev.zoom.aop.factory.SetterFactory;
import org.zoomdev.zoom.common.caster.CasterException;
import org.zoomdev.zoom.common.exceptions.ZoomException;
import org.zoomdev.zoom.common.utils.Classes;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class BeanJsonReader {

    private static final JsonFactory jsonFactory = FactoryHolder.jsonFactory;
    private static final Map<Type, ValueAdapter> cache = new ConcurrentHashMap<>();
    private static StringAdapter STRING_ADAPTER = new StringAdapter();
    private static BooleanAdapter BOOLEAN_ADAPTER = new BooleanAdapter();
    private static IntAdapter INT_ADAPTER = new IntAdapter();
    private static LongAdapter LONG_ADAPTER = new LongAdapter();
    private static BigIntegerAdapter BIG_INTEGER_ADAPTER = new BigIntegerAdapter();
    private static BigDecimalAdapter BIG_DECIMAL_ADAPTER = new BigDecimalAdapter();

    private BeanJsonReader() {

    }

    public static ValueAdapter getAdapter(Type type) {
        return cache.computeIfAbsent(type, k -> createAdapter(type));
    }

    public static Object read(String str, Type type) throws IOException {
        try (JsonParser jp = jsonFactory.createJsonParser(str)) {
            jp.nextToken();
            return getAdapter(type).adapt(jp);
        }
    }

    public static Object read(String str, ValueAdapter adapter, Type type) throws IOException {
        try (JsonParser jp = jsonFactory.createJsonParser(str)) {
            jp.nextToken();
            return adapter.adapt(jp);
        }
    }

    public static Object read(InputStream str, Type type) throws IOException {
        try (JsonParser jp = jsonFactory.createJsonParser(str)) {
            jp.nextToken();
            return getAdapter(type).adapt(jp);
        }
    }

    public static Object read(Reader str, Type type) throws IOException {
        try (JsonParser jp = jsonFactory.createJsonParser(str)) {
            jp.nextToken();
            return getAdapter(type).adapt(jp);
        }
    }

    private static BeanJsonParser createBeanAdapter(Class type) {
        Constructor constructor = ConstructorFactory.FACTORY.create(type);
        List<Field> fields = Classes.getFields(type);
        Map<String, Pair> fieldMap = new HashMap<>();


        for (int i = 0, c = fields.size(); i < c; ++i) {
            Field f = fields.get(i);
            Setter setter = SetterFactory.FACTORY.create(f);
            ValueAdapter adapter = createAdapter(f.getGenericType());
            Pair pair = new Pair();
            pair.setter = setter;
            pair.adapter = adapter;
            fieldMap.put(f.getName(), pair);
        }

        BeanPropertyMap map = new BeanPropertyMap(fieldMap);
        return new BeanJsonParser(constructor, map);
    }

    private static ValueAdapter createAdapter(Class clazz) {
        // 基本类型处理
        if (clazz == String.class) {
            return STRING_ADAPTER;
        } else if (clazz == Integer.class || clazz == Integer.TYPE) {
            return INT_ADAPTER;
        } else if (clazz == Long.class || clazz == Long.TYPE) {
            return LONG_ADAPTER;
        } else if (clazz == Boolean.class || clazz == Boolean.TYPE) {
            return BOOLEAN_ADAPTER;
        } else if (clazz == BigDecimal.class) {
            return BIG_DECIMAL_ADAPTER;
        } else if (clazz == BigInteger.class) {
            return BIG_INTEGER_ADAPTER;
        } else if (clazz.isEnum()) {
            return new EnumAdapter(clazz);
        }
        return createBeanAdapter(clazz);
    }

    private static ValueAdapter createAdapter(Type type) {
        if (type instanceof Class) {
            return createAdapter((Class) type);
        } else if (type instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) type;
            Class rawType = (Class) pt.getRawType();
            if (List.class.isAssignableFrom(rawType)) {
                return new ListAdapter(createAdapter(pt.getActualTypeArguments()[0]));
            }
        }
        throw new ZoomException("不能支持的类型:" + type);
    }

    private static class EnumAdapter extends ValueAdapter {
        private final Class<? extends Enum> enumType;

        EnumAdapter(Class<?> enumType) {
            this.enumType = (Class<? extends Enum>) enumType;
        }

        @Override
        Object adapt(JsonParser jp) throws IOException {
            String value = jp.getText();
            return Enum.valueOf(enumType, value);
        }
    }


    public static abstract class ValueAdapter {
        abstract Object adapt(JsonParser jp) throws IOException;
    }

    private static class ListAdapter extends ValueAdapter {

        private final ValueAdapter adapter;

        public ListAdapter(ValueAdapter adapter) {
            super();
            this.adapter = adapter;
        }

        @Override
        Object adapt(JsonParser jp) throws IOException {
            List list = new ArrayList();
            while ((jp.nextToken()) != JsonToken.END_ARRAY) {
                list.add(adapter.adapt(jp));
            }
            return list;
        }
    }

    private static class StringAdapter extends ValueAdapter {
        @Override
        Object adapt(JsonParser jp) throws IOException {
            return jp.getText();
        }
    }

    private static class IntAdapter extends ValueAdapter {
        @Override
        Object adapt(JsonParser jp) throws IOException {
            return jp.getIntValue();
        }
    }

    private static class LongAdapter extends ValueAdapter {
        @Override
        Object adapt(JsonParser jp) throws IOException {
            return jp.getLongValue();
        }
    }

    private static class BigIntegerAdapter extends ValueAdapter {
        @Override
        Object adapt(JsonParser jp) throws IOException {
            return jp.getBigIntegerValue();
        }
    }

    private static class BigDecimalAdapter extends ValueAdapter {
        @Override
        Object adapt(JsonParser jp) throws IOException {
            return jp.getDecimalValue();
        }
    }

    private static class BooleanAdapter extends ValueAdapter {
        @Override
        Object adapt(JsonParser jp) throws IOException {
            JsonToken token = jp.getCurrentToken();
            if (token == JsonToken.VALUE_TRUE) {
                return Boolean.TRUE;
            } else if (token == JsonToken.VALUE_FALSE) {
                return Boolean.FALSE;
            }
            throw new CasterException("json不能转化为bool值" + token.name());
        }
    }


    private static class Pair {
        ValueAdapter adapter;
        Setter setter;

        void handle(JsonParser parser, Object bean) throws IOException {
            Object value = adapter.adapt(parser);
            setter.set(bean, value);
        }
    }

    private static class BeanJsonParser extends ValueAdapter {


        private final Constructor constructor;
        private final BeanPropertyMap fieldMap;


        public BeanJsonParser(Constructor constructor, BeanPropertyMap fieldMap) {
            this.constructor = constructor;
            this.fieldMap = fieldMap;
        }


        @Override
        Object adapt(JsonParser jp) throws IOException {
            Object bean = constructor.newInstance();
            JsonToken t = jp.getCurrentToken();
            if (t == JsonToken.START_OBJECT) {
                t = jp.nextToken();
            }
            for (; t == JsonToken.FIELD_NAME; t = jp.nextToken()) {
                String key = jp.getCurrentName();
                JsonToken token = jp.nextToken(); // 移动到字段值
                if (token == JsonToken.VALUE_NULL) {
                    continue;
                }
                Pair pair = fieldMap.find(key);
                pair.handle(jp, bean);
            }
            return bean;
        }
    }

    private static final class BeanPropertyMap {
        private final Bucket[] _buckets;

        private final int _hashMask;

        private final int _size;

        public BeanPropertyMap(Map<String, Pair> properties) {
            _size = properties.size();
            int bucketCount = findSize(_size);
            _hashMask = bucketCount - 1;
            Bucket[] buckets = new Bucket[bucketCount];
            for (Map.Entry<String, Pair> entry : properties.entrySet()) {
                String key = entry.getKey();
                int index = key.hashCode() & _hashMask;
                buckets[index] = new Bucket(buckets[index], key, entry.getValue());
            }
            _buckets = buckets;
        }

        public int size() {
            return _size;
        }

        public Pair find(String key) {
            int index = key.hashCode() & _hashMask;
            Bucket bucket = _buckets[index];
            // Let's unroll first lookup since that is null or match in 90+% cases
            if (bucket == null) {
                return null;
            }
            // Primarily we do just identity comparison as keys should be interned
            if (bucket.key == key) {
                return bucket.value;
            }
            while ((bucket = bucket.next) != null) {
                if (bucket.key == key) {
                    return bucket.value;
                }
            }
            // Do we need fallback for non-interned Strings?
            return _findWithEquals(key, index);
        }

        private final static int findSize(int size) {
            // For small enough results (32 or less), we'll require <= 50% fill rate; otherwise 80%
            int needed = (size <= 32) ? (size + size) : (size + (size >> 2));
            int result = 2;
            while (result < needed) {
                result += result;
            }
            return result;
        }

        /*
        /**********************************************************
        /* Helper methods
        /**********************************************************
         */

        private Pair _findWithEquals(String key, int index) {
            Bucket bucket = _buckets[index];
            while (bucket != null) {
                if (key.equals(bucket.key)) {
                    return bucket.value;
                }
                bucket = bucket.next;
            }
            return null;
        }


        private final static class Bucket {
            public final Bucket next;
            public final String key;
            public final Pair value;

            public Bucket(Bucket next, String key, Pair value) {
                this.next = next;
                this.key = key;
                this.value = value;
            }
        }

    }
}
